function Gameboard(target, control, playerCount) {
	this.target = target;
	this.control = control;
	this.playerCount = playerCount;
	
	// Find all the elements we're interested now, for efficiency
	this.gameboard = this.target.findName("Gameboard");
	this.suitsScoreDisplay = this.target.findName("SuitsScore");
	this.cowboyScoreDisplay = this.target.findName("CowboyScore");
	this.perFrameStoryboard = this.target.findName("PerFrameGenerator");
	this.introScreen = this.target.findName("Intro");
	this.root = this.target.findName("Root");
	this.introIntroAnimation = this.target.findName("IntroIntro");
	this.introOutroAnimation = this.target.findName("IntroOutro");
	this.continueIntroAnimation = this.target.findName("ContinueIntro");
	this.winnerIntroAnimation = this.target.findName("WinnerIntro");
	this.loserIntroAnimation = this.target.findName("LoserIntro");
	
	this.animationCallbacks = new Array();
	
	// Hook up necessary events
	setCallback(this.introScreen, "MouseLeftButtonDown", delegate(this, this.handleGameStartClick));
	setCallback(this.introOutroAnimation, "Completed", delegate(this, this.handleIntroOutroCompleted));
	
	this.configureTimer();
	
	this.loadHexes();
}

Gameboard.prototype.suitXaml = null;
Gameboard.prototype.cowboyXaml = null;
Gameboard.prototype.markerXaml = null;
Gameboard.prototype.loader = null;
Gameboard.prototype.cols = 9;
Gameboard.prototype.rows = 9;
Gameboard.prototype.suitsScore = 0;
Gameboard.prototype.cowboyScore = 0;
Gameboard.prototype.movingPlayers = 0;
Gameboard.prototype.openSpaces = 0;
Gameboard.prototype.lastHex = null;
Gameboard.prototype.techyXaml = null;
Gameboard.prototype.levelIndex = -1;
Gameboard.prototype.partsLoaded = false;
Gameboard.prototype.started = false;

Gameboard.prototype.loadHexes = function() {
	
	// Helper class that loads all the XAML asynchronously then provides
	// a single callback when it's all loaded.
	this.loader = new Loader("xaml");
	this.loader.uris.push("Concrete1.xaml");
	this.loader.uris.push("Concrete2.xaml");
	this.loader.uris.push("Concrete3.xaml");
	this.loader.uris.push("Grass1.xaml");
	this.loader.uris.push("Grass2.xaml");
	this.loader.uris.push("Grass3.xaml");
	this.loader.uris.push("Suit.xaml");
	this.loader.uris.push("Cowboy.xaml");
	this.loader.uris.push("ActiveMarker.xaml");
	this.loader.uris.push("Sprawl1.xaml");
	this.loader.uris.push("Sprawl2.xaml");
	this.loader.uris.push("Sprawl3.xaml");
	this.loader.uris.push("Techy.xaml");
	this.loader.completed = delegate(this, this.handleXamlLoaded);
	this.loader.start();
}

/* Called from the loader when all the contents are finished loading.
 * The loader is really dumb and assumes it always works.
 */
Gameboard.prototype.handleXamlLoaded = function(responses) {	
	this.suitXaml = responses["Suit.xaml"];
	this.cowboyXaml = responses["Cowboy.xaml"];
	this.techyXaml = responses["Techy.xaml"];
	this.markerXaml = responses["ActiveMarker.xaml"];
	
	this.concretes = new Array();
	this.concretes.push(responses["Concrete1.xaml"]);
	this.concretes.push(responses["Concrete2.xaml"]);
	this.concretes.push(responses["Concrete3.xaml"]);
	
	this.grasses = new Array();
	this.grasses.push(responses["Grass1.xaml"]);
	this.grasses.push(responses["Grass2.xaml"]);
	this.grasses.push(responses["Grass3.xaml"]);
	
	this.sprawls = new Array();
	this.sprawls.push(responses["Sprawl1.xaml"]);
	this.sprawls.push(responses["Sprawl2.xaml"]);
	this.sprawls.push(responses["Sprawl3.xaml"]);
	
	//All the xaml parts are loaded, show the 'click here to begin'
	this.partsLoaded = true;
	this.continueIntroAnimation.begin();
}

/* User clicked intro screen to start the game */
Gameboard.prototype.handleGameStartClick = function() {
	// Wait for all the parts to download.
	if (this.partsLoaded && !this.started) {
		this.started = true;

		// Animate out the intro
		this.introOutroAnimation.begin();
		this.introIntroAnimation.stop();
	}
}

/* Called when the intro is done animating and the game needs to begin */
Gameboard.prototype.handleIntroOutroCompleted = function() {
	this.introOutroAnimation.stop();
	this.continueIntroAnimation.stop();
	this.root.children.remove(this.introScreen);
	
	this.nextLevel();
}

/* Starts a new game with the specified characters
 * Techys are real timid and only move short distances, but they're quite crafty
 * Suits move longer distances, but go for more points.
 * Cowboys are controlled by the user.
 * @param techyCount #of techys that are on the screen
 * @param suitCount #of suits on the screen
 * @param cowboyCount #of cowboys on the screen
 */
Gameboard.prototype.newGame = function(techyCount, suitCount, cowboyCount) {
	while(this.gameboard.children.count > 0)
		this.gameboard.children.removeAt(0);
		
	this.hexes = new Array();
	this.players = new Array();
	
	this.openSpaces = 0;
	this.movingPlayers = 0;
	this.addToSuitsScore(-this.suitsScore);
	this.addToCowboyScore(-this.cowboyScore);
	
	for (var x = 0; x < this.rows; ++x)
		this.hexes[x] = new Array();
	
	for (var y = 0; y < this.cols; ++y) {
		for (var x = 0; x < this.rows; ++x) {
		
			var hexElement;
			
			var value = Math.round(Math.random() * 2);
			hexElement = this.control.createFromXaml(this.concretes[value]);
			
			this.gameboard.children.add(hexElement);
			this.hexes[x][y] = new Hex(this, hexElement, x, y, value + 1);
		}
	}
	
	for (var i = 0; i < techyCount; ++i) {
		var techyGraphic = this.control.createFromXaml(this.techyXaml);
		this.gameboard.children.add(techyGraphic);
		var techy = new Player(this, techyGraphic, this.sprawls);
		techy.setPosition(0, Math.floor(this.rows / 2) + (i - Math.floor((techyCount + suitCount) / 2) ) * 2);
		techy.scoreCallback = delegate(this, this.addToSuitsScore);
		
		// Change techy's strategy to be lame :)
		techy.findBestPlay = techy.timidFindBestPlay;
		
		this.players.push(techy);
		
		if (this.playerCount < 2)
			techy.isAutoPlayer = true;
	}
	
	for (var i = 0; i < suitCount; ++i) {
		var suitGraphic = this.control.createFromXaml(this.suitXaml);
		this.gameboard.children.add(suitGraphic);
		var suit = new Player(this, suitGraphic, this.sprawls);
		suit.setPosition(0, Math.floor(this.rows / 2) + (i - Math.floor((suitCount + techyCount) / 2) ) * 2);
		suit.scoreCallback = delegate(this, this.addToSuitsScore);
		this.players.push(suit);
		
		if (this.playerCount < 2)
			suit.isAutoPlayer = true;
	}
	
	for (var i = 0; i < cowboyCount; ++i) {
		var cowboyGraphic = this.control.createFromXaml(this.cowboyXaml);
		this.gameboard.Children.Add(cowboyGraphic);
		var cowboy = new Player(this, cowboyGraphic, this.grasses);
		cowboy.setPosition(this.cols - 1, Math.floor(this.rows / 2) + (i - Math.floor(cowboyCount / 2) ) * 2);
		cowboy.scoreCallback = delegate(this, this.addToCowboyScore);
			
		this.players.push(cowboy);
		
		if (this.playerCount == 0)
			cowboy.isAutoPlayer = true;
	}
	
	this.currentPlayer = 0;
	this.players[this.currentPlayer].activate();
	
}

/* Adds the value to the suits score and updates the score in the UI. */
Gameboard.prototype.addToSuitsScore = function(value) {
	this.suitsScore += value;
	this.suitsScoreDisplay.text = "" + this.suitsScore;
}

/* Adds the value to the cowboy score and updates the score in the UI. */
Gameboard.prototype.addToCowboyScore = function(value) {
	this.cowboyScore += value;
	this.cowboyScoreDisplay.text = "" + this.cowboyScore;
}

/* Try to move the current player to the specified hex.
 * This will do nothing if the hex is not a valid move.
 */
Gameboard.prototype.tryMoveTo = function(hex) {
	var currentPlayer = this.players[this.currentPlayer];
	
	if (currentPlayer.isAnimating())
		return;
	
	if (this.canMoveTo(hex, currentPlayer, currentPlayer.getCurrentHex())) {
		currentPlayer.addScore(hex.value);
		currentPlayer.deactivate();
		
		currentPlayer.animatePlayerTo(hex.x, hex.y);
		
		if (!currentPlayer.isAutoPlayer)
			this.clearSemiSelect();
	}
}

/* Advance to the next player.
 * Will take care of advancing the level etc if no players can move.
 */
Gameboard.prototype.nextPlayer = function() {
	var currentPlayer = this.players[this.currentPlayer];
	currentPlayer.deactivate();

	// Here as a shortcut to show the win screen. It's not all that great, if you're curious.	
//	if (!currentPlayer.isAutoPlayer) {
//		this.showWinnar();
//		return;
//	}
	
	var anyAutoPlayers = false;
	for (var i = 0; i < this.players.length; ++i) {
		var player = this.players[i];
		if (player.isAutoPlayer && player.canMove) {
			anyAutoPlayers = true;
			break;
		}
	}
	
	if (!anyAutoPlayers && this.cowboyScore > this.suitsScore) {
		this.levelEnded();
		return;
	}
	
	if (this.movingPlayers != 0) {
		++this.currentPlayer;
		if (this.currentPlayer >= this.players.length)
			this.currentPlayer = 0;
			
		this.players[this.currentPlayer].activate();
		
		if (this.lastHex != null)
			this.trySelectTo(this.lastHex);
	}
	else
		this.levelEnded();
}

/* Called when the current level is done and the next level should be started */
Gameboard.prototype.levelEnded = function() {
	if (this.suitsScore > this.cowboyScore)
		this.showLosar();
	else
		this.nextLevel();
}

/* Advance the game to the next level, or announce winner if no levels left */
Gameboard.prototype.nextLevel = function() {
	++this.levelIndex;
	if (this.levelIndex == 0) {
		this.newGame(1, 0, 1);
	}
	else if (this.levelIndex == 1) {
		this.newGame(0, 1, 1);
	}
	else if (this.levelIndex == 2) {
		this.newGame(2, 0, 1);
	}
	else if (this.levelIndex == 3) {
		this.newGame(0, 2, 2);
	}
	else if (this.levelIndex == 4) {
		this.newGame(0, 2, 1);
	}
	else
		this.showWinnar();
}

/* Show the 'You Win' screen */
Gameboard.prototype.showWinnar = function() {
	this.root.children.add(this.introScreen);
	this.winnerIntroAnimation.begin();
}

/* Show the 'You Lose' screen */
Gameboard.prototype.showLosar = function() {
	this.root.children.add(this.introScreen);
	this.loserIntroAnimation.begin();
}

Gameboard.prototype.decrementMovingPlayers = function() {
	--this.movingPlayers;
}

/* Highlight the path to the specified hex if it is a valid move
 * This will show the score for the hex if it is valid and add the
 * yellow highlight to hexes in the middle.
 */
Gameboard.prototype.trySelectTo = function(hex) {
	var currentPlayer = this.players[this.currentPlayer];
	if (currentPlayer.isAnimating()) {
		this.lastHex = hex;
		return;
	}
		
	if (this.canMoveTo(hex, currentPlayer, currentPlayer.getCurrentHex())) {
		var selector = function(hex) {
			hex.setMode("SemiSelected");
		}
		
		this.walk(currentPlayer.x, currentPlayer.y, hex.x, hex.y, selector);
		
		hex.showScore();
	}
	this.lastHex = null;
}

/* Checks to see if the player can move to the specified position */
Gameboard.prototype.canMoveTo = function(hex, player, playerLocation) {
	var isValid = true;
	var checker = function(hex) {
		if (hex.occupier != null) {
			isValid = false;
		}
	}
	if (this.walk(playerLocation.x, playerLocation.y, hex.x, hex.y, checker) && isValid) {
		return true;
	}
	return false
}

/* Clears all the highlighted hexes that show whether the move is valid or not. */
Gameboard.prototype.clearSemiSelect = function() {
	for (var x = 0; x < this.hexes.length; ++x) {
		var row = this.hexes[x];
		for (var y = 0; y < row.length; ++y) {
			var hex = row[y];
			if (hex.mode == "SemiSelected")
				hex.setMode("Deselected");
		}
	}
	this.lastHex = null;
}

/* Calls the specified callback for every hex within maxRadius of hex
 * This is a helper method for iterating all the possible moves
 * Callback is expected to return true or false whether to advance the
 * radius further, allows an early exit if the direction is invalid after a certain point.
 */
Gameboard.prototype.walkPossibilities = function(hex, maxRadius, callback) {
	for (var radius = 1; radius <= maxRadius; ++radius) {
		var y = hex.y + radius;
		var x = radius / 2;
		if (hex.y % 2 == 1)
			x += .5;
		var x = Math.floor(x) + hex.x;
		
		if (x >= 0 && x < this.cols && y >= 0 && y < this.rows) {
			if (!callback(this.hexes[x][y]))
				break;
		}
	}
	
	for (var radius = 1; radius <= maxRadius; ++radius) {
		var y = hex.y + radius;
		var x = radius / 2;
		if (hex.y % 2 == 0)
			x += .5;
		x = hex.x - Math.floor(x);
		
		if (x >= 0 && x < this.cols && y >= 0 && y < this.rows) {
			if (!callback(this.hexes[x][y]))
				break;
		}
	}
	
	for (var radius = 1; radius <= maxRadius; ++radius) {
		var y = hex.y - radius;
		x = radius / 2;
		if (hex.y % 2 == 1)
			x += .5;
		x = Math.floor(x) + hex.x;
		
		if (x >= 0 && x < this.cols && y >= 0 && y < this.rows) {
			if (!callback(this.hexes[x][y]))
				break;
		}
	}
	
	for (var radius = 1; radius <= maxRadius; ++radius) {
		var y = hex.y - radius;
		x = radius / 2;
		if (hex.y % 2 == 0)
			x += .5;
		x = hex.x - Math.floor(x);
		
		if (x >= 0 && x < this.cols && y >= 0 && y < this.rows) {
			if (!callback(this.hexes[x][y]))
				break;
		}
	}
	
	y = hex.y;
	
	for (var radius = 1; radius <= maxRadius; ++radius) {
		x = hex.x + radius;
		if (x >= 0 && x < this.cols && y >= 0 && y < this.rows) {
			if (!callback(this.hexes[x][y]))
				break;
		}
	}
	
	for (var radius = 1; radius <= maxRadius; ++radius) {
		x = hex.x - radius;
		if (x >= 0 && x < this.cols && y >= 0 && y < this.rows) {
			if (!callback(this.hexes[x][y]))
				break;
		}
	}
}

/* Call the provided callback for every hex between the two specified points
 * This will return false if there is no path between the two points.
 */
Gameboard.prototype.walk = function(xo, yo, xf, yf, handler) {
	if (yo == yf) {
		for (var x = xo; x != xf; x += (xo < xf ? 1 : -1)) {
			if (x != xo)
				handler(this.hexes[x][yf]);
		}
		handler(this.hexes[xf][yf]);
		return true;
	}
	else {
		var yOffset = (yf - yo) / 2;
		if (yo % 2 == 1)
			yOffset += .5;
			
		if (xf - xo == Math.floor(yOffset)) {
			
			for (var y = yo; y != yf; y += (yo < yf ? 1 : -1)) {
				var x = (y - yo) / 2;
				if (y % 2 == 0)
					x += .5;
				x = Math.floor(x) + xo;
				
				if (!(x == xo && y == yo))
					handler(this.hexes[x][y]);
			}
			handler(this.hexes[xf][yf]);
			return true;
		}
		else {
			yOffset = (yf - yo) / 2;
			if (yo % 2 == 0)
				yOffset += .5;
				
			if (xo - xf == Math.floor(yOffset)) {
				for (var y = yo; y != yf; y += (yo < yf ? 1 : -1)) {
					var x = (y - yo) / 2;
					if (yo % 2 == 0)
						x += .5;
					x = xo - Math.floor(x);
					
					if (!(x == xo && y == yo))
						handler(this.hexes[x][y]);
				}
				handler(this.hexes[xf][yf]);
				return true;
			}
		}
	}
	return false;
}

/* Single animation callback to handle the manual player animations. */
Gameboard.prototype.handlePerFrameTick = function() {
	for (var i = 0; i < this.animationCallbacks.length; ++i)
		this.animationCallbacks[i].handleAnimationUpdate();
}

/* Used for the manual player animation */
Gameboard.prototype.startAnimation = function(handler) {
	this.animationCallbacks.push(handler);
	this.perFrameStoryboard.begin();
}

/* Used for the manual player animation */
Gameboard.prototype.stopAnimation = function(handler) {
	for (var i = 0; i < this.animationCallbacks.length; ++i)
		if (this.animationCallbacks[i] == handler) {
			this.animationCallbacks.splice(i, 1);
			break;
		}
}

/* Sets up the per-frame timer callbacks for manual animations */
Gameboard.prototype.configureTimer = function() {
	// on Firefox use the window's interval handler, prevents FF from generating unresponsive script warnings
	if (navigator.userAgent.indexOf("Firefox")!=-1) {
		window.setInterval(delegate(this, this.handleIntervalTick), 30);
	}
	// use a zero-duration storyboard on IE so the animation is smoother.
	else {
		setCallback(this.perFrameStoryboard, "Completed", delegate(this, this.handleStoryboardTick));
	}
}

Gameboard.prototype.handleStoryboardTick = function() {
	this.handlePerFrameTick();
	this.perFrameStoryboard.begin();
}

Gameboard.prototype.handleIntervalTick = function() {
	this.handlePerFrameTick();
}